package main.java.LeetCode.ClassicalAlgorithm.Queue.Impl;

import main.java.LeetCode.ClassicalAlgorithm.Queue.Queue;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class BlockQueueGroup <E> implements Queue<E> {

    private final ArrayList<LockedBlockQueue<E>> list;

    private final ThreadLocal<Integer> index;
    private final ThreadLocal<Integer> flag;
    private final int groupCap;

    private final ReentrantLock mainLock = new ReentrantLock();
    private final Condition isEmpty = mainLock.newCondition();
    private final Condition isFull = mainLock.newCondition();

    public BlockQueueGroup (int groupCap,int queueCap){
        this.list = new ArrayList<>(groupCap);
        this.groupCap = groupCap;
        for(int i = 0;i<groupCap;i++)
            list.add(new LockedBlockQueue<E>(queueCap));
        index = new ThreadLocal<>();
        flag = new ThreadLocal<>();
    }

    private void lockFull() throws InterruptedException {
        mainLock.lock();
        isEmpty.signal();

        try {
            isFull.await();
        }finally {
            mainLock.unlock();
        }
    }
    private void lockEmpty() throws InterruptedException{
        mainLock.lock();
        isFull.signal();

        try {
            isEmpty.await();
        }finally {
            mainLock.unlock();
        }
    }
    private int lockEmpty(long timeout,TimeUnit unit) throws InterruptedException{
        mainLock.lock();
        isFull.signal();
        long nanos = unit.toNanos(timeout);

        try {
            nanos = isEmpty.awaitNanos(nanos);
            if(nanos<0)
                return -1;
            else
                return 1;
        }finally {
            mainLock.unlock();
        }
    }
    private void awakeEmpty(){
        mainLock.lock();
        try {
            isEmpty.signal();
        }finally {
            mainLock.unlock();
        }
    }
    private void awakeFull(){
        mainLock.lock();
        try {
            isFull.signal();
        }finally {
            mainLock.unlock();
        }
    }

    @Override
    public E get() throws InterruptedException {
        if(index.get()==null)
            index.set(0);

        int temp = index.get();
        int loop = 0;
        E e;
        while ((e = list.get(temp%groupCap).get())==null){
            temp++;
            loop++;
            if(loop==groupCap){
                lockEmpty();
                loop = 0;
            }
            if(temp==Integer.MAX_VALUE)
                temp = 0;
        }
        index.set(temp);
        awakeFull();

        return e;
    }

    @Override
    public boolean set(E e) throws InterruptedException {
        if(flag.get()==null)
            flag.set(0);

        int temp = flag.get();
        boolean isSet = !(list.get(temp%groupCap).set(e));
        int index = 0;
        while (isSet){
            if(index==groupCap){
                lockFull();
                index = 0;
            }
            index++;
            temp++;
            isSet = !(list.get(temp%groupCap).set(e));
        }
        awakeEmpty();
        return true;
    }

    @Override
    public E get(long timeout, TimeUnit unit) throws InterruptedException {
        if(index.get()==null)
            index.set(0);

        int temp = index.get();
        int loop = 0;
        E e;
        while ((e = list.get(temp%groupCap).get())==null){
            temp++;
            loop++;
            if(loop==groupCap){
                if(lockEmpty(timeout,unit)<0)
                    return e;
                loop = 0;
            }
            if(temp==Integer.MAX_VALUE)
                temp = 0;
        }
        index.set(temp);
        awakeFull();

        return e;
    }

    @Override
    public boolean offer(E e) {
        if(flag.get()==null)
            flag.set(0);

        int temp = flag.get();
        int index = 0;
        boolean isSet = !(list.get(temp%groupCap).offer(e));

        while (index<groupCap&&isSet){
            index++;
            temp++;
            if(temp==Integer.MAX_VALUE)
                temp = 0;
            isSet = !(list.get(temp%groupCap).offer(e));
        }
        if(!isSet){
            awakeEmpty();
            return true;
        }

        flag.set(temp);
        isSet = list.get(temp%groupCap).set(e);
        if(isSet){
            awakeEmpty();
        }
        return isSet;
    }

    @Override
    public int size() {
        return this.groupCap;
    }
}
